/**
 * @author Dream-Lab مۇختەرجان مەخمۇت
 * @em&&ail ug-project@outlook.com
 * @create date 2022-07-24 19:45:13
 * @modify date 2022-07-24 19:45:13
 * @desc [description]
*/


use std::{time::{Instant, Duration}, future::Future};
use gen::mini_mulazim::{Empty, Id, restaurant_service_client::RestaurantServiceClient, order_service_client::OrderServiceClient, rider_service_client::RiderServiceClient};
use gen::mini_mulazim::restaurant_service_server::RestaurantService;
use rand::Rng;
use shrust::{Shell, ShellIO};
use tonic::{transport::{Server, Endpoint}, client};
use crate::gen::mini_mulazim::{account_service_client::AccountServiceClient, KeysRequest, CreateUserRequest, CreateRestaurantRequest, CreateOrderRequest, CreateRiderRequest, OrderDeliveryRequest};

mod server;
mod gen;

async fn measure_time_mills<F, T>(f: F) -> (u64, T) where F: Future<Output = T> {
    let instant = Instant::now();
    let result = f.await;
    (instant.elapsed().as_millis() as u64, result)
}

#[tokio::main]
async fn main() {
    let endpoint = Endpoint::from_static("http://0.0.0.0:7999").http2_keep_alive_interval(Duration::from_secs(100)).concurrency_limit(1000);
    println!("Almas software technologies 2022");
    println!("please type 'help' to get help.");

    let mut shell = Shell::new(0);
    register_account_commands(&mut shell, endpoint.clone()).await;
    register_restaurant_commands(&mut shell, endpoint.clone()).await;
    register_order_commands(&mut shell, endpoint.clone()).await;
    register_rider_commands(&mut shell, endpoint.clone()).await;
    shell.run_loop(&mut ShellIO::default());
}

async fn register_account_commands<T>(shell: &mut Shell<T>, endpoint: Endpoint) {
    let mut account_service = AccountServiceClient::new(endpoint.clone().connect().await.expect("Cannot connect to server"));
    {
        let client = account_service.clone();
        shell.new_command_noargs("create_user", "Concurrently creates 100_000 user", move |a, b|{
            let mut client = client.clone();
            tokio::spawn(async move {
                let start = Instant::now();
                let mut start = Instant::now();
                for i in 0..100_000 {
                    let result = client.create(CreateUserRequest {
                        user_name: format!("ئىشلەتكۈچى: {}", i),
                    }).await.expect("Server respond a error");
                    let user = result.into_inner();
                    if i % 100 == 0 {
                        println!("{} User is created in {} Ms.", i, start.elapsed().as_millis());
                        start = Instant::now();
                    }
                }
                println!("Done.");
            });
            Ok(())
        });
    }
    {
        let client = account_service.clone();
        shell.new_command_noargs("list_users_key", "List 1_000_000 users key", move |_, _|{
            let mut client = client.clone();
            tokio::spawn(async move {
                let result = client.list(KeysRequest { from: Some(0), count: 1_000_000.into() }).await.expect("Server responds with error");
                let keys = result.into_inner().keys;
                println!("keys: [{}]", keys.join(", "));
                println!("Users count is: {}", keys.len());
            });
            Ok(())
        });
    }
    {
        let client = account_service.clone();
        shell.new_command_noargs("list_random_users", "Randomly get 1_000 user at the same time and repeat 100 times so all randomly listed users count is corresponds 100_000.", move |_, _|{
            let mut client = client.clone();
            tokio::spawn(async move {
                let (millis, _) = measure_time_mills(async {
                    let tasks = Vec::from_iter((0..100u16).map(|index|{
                        let mut client = client.clone();
                        println!("Start of task {}", index);
                        tokio::spawn(async move {
                            let keys = client.list(KeysRequest::default()).await.expect("Server responds error result").into_inner().keys;
                            if keys.len() == 0 {
                                println!("There are no users.");
                                return;
                            }
                            let (milllis, _) = measure_time_mills(async {
                                for i in 0..1000u16 {
                                    let random_key = {
                                        keys.get(rand::thread_rng().gen_range(0..keys.len())).expect("Cannot get random key")
                                    };
                                    let user = client.get_by_id(Id{uuid: random_key.clone()}).await.expect("Cannot get user").into_inner();
                                    // if i % 100 == 0 {
                                    //     println!("Completed {} requests of task {}", i, index);
                                    // }
                                }
                            }).await;
                            println!("{}. Get random 1_000 user takes {} milliseconds", index + 1, milllis);
                        })
                    }));
                    for (index, task) in tasks.into_iter().enumerate() {
                        task.await.unwrap();
                    }
                }).await;
                println!("Done with {} millisecond.", millis);
            });
            Ok(())
        });
    }
    {
        let client = account_service.clone();
        shell.new_command("get_user_by_id", "Randomly get 1_000 user at the same time and repeat 20 times so all randomly listed users count is corresponds 200_000.", 1, move |_, _, args|{
            let mut client = client.clone();
            let uuid = args.first().unwrap().to_string();
            tokio::spawn(async move {
                let (millis, _) = measure_time_mills(async {
                    if let Ok(user) = client.get_by_id(Id{uuid: uuid.clone()}).await {
                        println!("{}", serde_json::to_string_pretty(&user.into_inner()).expect("Cannot serialize to json"));
                    } else {
                        println!("There is no such users of uuid: {}", uuid);
                    }
                }).await;
                println!("Done with {} milliseconds.", millis);
            });
            Ok(())
        });
    }
}

async fn register_restaurant_commands<T>(shell: &mut Shell<T>, endpoint: Endpoint) {
    let mut account_service = RestaurantServiceClient::new(endpoint.clone().connect().await.expect("Cannot connect to server"));
    {
        let client = account_service.clone();
        shell.new_command_noargs("create_restaurant", "Concurrently creates 1000 restaurant", move |a, b|{
            let mut client = client.clone();
            let endpoint = endpoint.clone();
            tokio::spawn(async move {
                let user_keys = AccountServiceClient::connect(endpoint.clone()).await.unwrap().list(KeysRequest::default()).await.expect("Server responds error result").into_inner().keys;
                let start = Instant::now();
                let mut start = Instant::now();
                for i in 0..1000 {
                    let random_key = {
                        (&user_keys).get(rand::thread_rng().gen_range(0..user_keys.len())).unwrap().clone()
                    };
                    let result = client.create(CreateRestaurantRequest {
                        name: format!("ئىشلەتكۈچى: {}", i),
                        owner_id: random_key,
                    }).await.expect("Server respond a error");
                    let restaurant = result.into_inner();
                    if i % 100 == 0 {
                        println!("{} Restaurant is created in {} Ms.", i, start.elapsed().as_millis());
                        start = Instant::now();
                    }
                }
                println!("Done.");
            });
            Ok(())
        });
    }
    {
        let client = account_service.clone();
        shell.new_command_noargs("list_restaurants_key", "List 1_000_000 restaurants key", move |_, _|{
            let mut client = client.clone();
            tokio::spawn(async move {
                let result = client.list(KeysRequest { from: Some(0), count: 1_000_000.into() }).await.expect("Server responds with error");
                let keys = result.into_inner().keys;
                println!("keys: [{}]", keys.join(", "));
                println!("Restaurant count is: {}", keys.len());
            });
            Ok(())
        });
    }
    {
        let client = account_service.clone();
        shell.new_command_noargs("list_random_restaurants", "Randomly get 1_000 restaurant at the same time and repeat 100 times so all randomly listed restaurant count is corresponds 100_000.", move |_, _|{
            let mut client = client.clone();
            tokio::spawn(async move {
                let (millis, _) = measure_time_mills(async {
                    let tasks = Vec::from_iter((0..100u16).map(|index|{
                        let mut client = client.clone();
                        println!("Start of task {}", index);
                        tokio::spawn(async move {
                            let keys = client.list(KeysRequest::default()).await.expect("Server responds error result").into_inner().keys;
                            if keys.len() == 0 {
                                println!("There are no restaurants.");
                                return;
                            }
                            let (milllis, _) = measure_time_mills(async {
                                for i in 0..1000u16 {
                                    let random_key = {
                                        keys.get(rand::thread_rng().gen_range(0..keys.len())).expect("Cannot get random key")
                                    };
                                    let restaurant = client.get_by_id(Id{uuid: random_key.clone()}).await.expect("Cannot get user").into_inner();
                                    // if i % 100 == 0 {
                                    //     println!("Completed {} requests of task {}", i, index);
                                    // }
                                }
                            }).await;
                            println!("{}. Get random 1_000 restaurant takes {} milliseconds", index + 1, milllis);
                        })
                    }));
                    for (index, task) in tasks.into_iter().enumerate() {
                        task.await.unwrap();
                    }
                }).await;
                println!("Done with {} millisecond.", millis);
            });
            Ok(())
        });
    }
    {
        let client = account_service.clone();
        shell.new_command("get_restaurant_by_id", "Randomly get 1_000 restaurant at the same time and repeat 20 times so all randomly listed restaurant count is corresponds 200_00.", 1, move |_, _, args|{
            let mut client = client.clone();
            let uuid = args.first().unwrap().to_string();
            tokio::spawn(async move {
                let (millis, _) = measure_time_mills(async {
                    if let Ok(user) = client.get_by_id(Id{uuid: uuid.clone()}).await {
                        let user = user.into_inner();
                        println!("{}", serde_json::to_string_pretty(&user).expect("Cannot serialize to json"));
                    } else {
                        println!("There is no such restaurant of uuid: {}", uuid);
                    }
                }).await;
                println!("Done with {} milliseconds.", millis);
            });
            Ok(())
        });
    }
}

async fn register_order_commands<T>(shell: &mut Shell<T>, endpoint: Endpoint) {
    let mut account_service = OrderServiceClient::new(endpoint.clone().connect().await.expect("Cannot connect to server"));
    {
        let client = account_service.clone();
        let endpoint = endpoint.clone();
        shell.new_command_noargs("create_order", "Concurrently creates 100_000 orders", move |a, b|{
            let mut client = client.clone();
            let endpoint = endpoint.clone();
            tokio::spawn(async move {
                let (user_keys, restaurant_keys) = {
                    (
                        AccountServiceClient::connect(endpoint.clone()).await.unwrap().list(KeysRequest::default()).await.expect("Server responds error result").into_inner().keys,
                        RestaurantServiceClient::connect(endpoint.clone()).await.unwrap().list(KeysRequest::default()).await.expect("Server responds error result").into_inner().keys,
                    )
                };
                let start = Instant::now();
                let mut start = Instant::now();
                for i in 0..100_000 {
                    let (random_user, random_restaurant) = {
                        (
                            (&user_keys).get(rand::thread_rng().gen_range(0..user_keys.len())).unwrap().clone(),
                            (&restaurant_keys).get(rand::thread_rng().gen_range(0..restaurant_keys.len())).unwrap().clone(),
                        )
                    };
                    let result = client.create(CreateOrderRequest {
                        foods_id: vec![],
                        user_id: random_user,
                        restaurant_id: random_restaurant
                    }).await.expect("Server respond a error");
                    let order = result.into_inner();
                    if i % 100 == 0 {
                        println!("{} Order is created in {} Ms.", i, start.elapsed().as_millis());
                        start = Instant::now();
                    }
                }
                println!("Done.");
            });
            Ok(())
        });
    }
    {
        let client = account_service.clone();
        shell.new_command_noargs("list_orders_key", "List 1_000_000 Orders key", move |_, _|{
            let mut client = client.clone();
            tokio::spawn(async move {
                let result = client.list(KeysRequest { from: Some(0), count: 1_000_000.into() }).await.expect("Server responds with error");
                let keys = result.into_inner().keys;
                println!("keys: [{}]", keys.join(", "));
                println!("Order count is: {}", keys.len());
            });
            Ok(())
        });
    }
    {
        let client = account_service.clone();
        shell.new_command_noargs("list_random_orders", "Randomly get 1_000 Order at the same time and repeat 100 times so all randomly listed order count is corresponds 100_000.", move |_, _|{
            let mut client = client.clone();
            tokio::spawn(async move {
                let (millis, _) = measure_time_mills(async {
                    let tasks = Vec::from_iter((0..100u16).map(|index|{
                        let mut client = client.clone();
                        println!("Start of task {}", index);
                        tokio::spawn(async move {
                            let keys = client.list(KeysRequest::default()).await.expect("Server responds error result").into_inner().keys;
                            if keys.len() == 0 {
                                println!("There are no orders.");
                                return;
                            }
                            let (milllis, _) = measure_time_mills(async {
                                for i in 0..1000u16 {
                                    let random_key = {
                                        keys.get(rand::thread_rng().gen_range(0..keys.len())).expect("Cannot get random key")
                                    };
                                    let order = client.get_by_id(Id{uuid: random_key.clone()}).await.expect("Cannot get user").into_inner();
                                    println!("{}", serde_json::to_string(&order).expect("Cannot serialize to json object"));
                                    // if i % 100 == 0 {
                                    //     println!("Completed {} requests of task {}", i, index);
                                    // }
                                }
                            }).await;
                            println!("{}. Get random 1_000 order takes {} milliseconds", index + 1, milllis);
                        })
                    }));
                    for (index, task) in tasks.into_iter().enumerate() {
                        task.await.unwrap();
                    }
                }).await;
                println!("Done with {} millisecond.", millis);
            });
            Ok(())
        });
    }
    {
        let client = account_service.clone();
        shell.new_command("get_order_by_id", "Randomly get 1_000 order at the same time and repeat 20 times so all randomly listed order count is corresponds 200_00.", 1, move |_, _, args|{
            let mut client = client.clone();
            let uuid = args.first().unwrap().to_string();
            tokio::spawn(async move {
                let (millis, _) = measure_time_mills(async {
                    if let Ok(user) = client.get_by_id(Id{uuid: uuid.clone()}).await {
                        println!("{}", serde_json::to_string_pretty(&user.into_inner()).expect("Cannot serialize to json"));
                    } else {
                        println!("There is no such order of uuid: {}", uuid);
                    }
                }).await;
                println!("Done with {} milliseconds.", millis);
            });
            Ok(())
        });
    }

    {
        let client = account_service.clone();
        let mut client = client.clone();
        shell.new_command_noargs("delivery_order", "Concurrently deliver 100_000 orders", move |a, b|{
            let mut client = client.clone();
            let endpoint = endpoint.clone();
            tokio::spawn(async move {
                let (rider_keys, order_keys) = {
                    (
                        RiderServiceClient::connect(endpoint.clone()).await.unwrap().list(KeysRequest::default()).await.expect("Server responds error result").into_inner().keys,
                        client.list(KeysRequest::default()).await.expect("Server responds error result").into_inner().keys,
                    )
                };
                let start = Instant::now();
                let mut start = Instant::now();
                for i in 0..100_000 {
                    let (random_rider, random_order) = {
                        (
                            (&rider_keys).get(rand::thread_rng().gen_range(0..rider_keys.len())).unwrap().clone(),
                            (&order_keys).get(rand::thread_rng().gen_range(0..order_keys.len())).unwrap().clone(),
                        )
                    };
                    let result = client.delivery(OrderDeliveryRequest {
                        order_id: random_order,
                        rider_id: random_rider,
                    }).await.expect("Server respond a error");
                    let order = result.into_inner();
                    if i % 100 == 0 {
                        println!("{} Order is created in {} Ms.", i, start.elapsed().as_millis());
                        start = Instant::now();
                    }
                }
                println!("Done.");
            });
            Ok(())
        });
    }
    
}


async fn register_rider_commands<T>(shell: &mut Shell<T>, endpoint: Endpoint) {
    let mut account_service = RiderServiceClient::new(endpoint.clone().connect().await.expect("Cannot connect to server"));
    {
        let client = account_service.clone();
        shell.new_command_noargs("create_rider", "Concurrently creates 1000 rider", move |a, b|{
            let mut client = client.clone();
            let endpoint = endpoint.clone();
            tokio::spawn(async move {
                let user_keys = AccountServiceClient::connect(endpoint.clone()).await.unwrap().list(KeysRequest::default()).await.expect("Server responds error result").into_inner().keys;
                let start = Instant::now();
                let mut start = Instant::now();
                for i in 0..1000 {
                    let random_key = {
                        (&user_keys).get(rand::thread_rng().gen_range(0..1000)).unwrap().clone()
                    };
                    let result = client.create(CreateRiderRequest {
                        // name: format!("ئىشلەتكۈچى: {}", i),
                        user_id: random_key,
                    }).await.expect("Server respond a error");
                    let rider = result.into_inner();
                    if i % 100 == 0 {
                        println!("{} Rider is created in {} Ms.", i, start.elapsed().as_millis());
                        start = Instant::now();
                    }
                }
                println!("Done.");
            });
            Ok(())
        });
    }
    {
        let client = account_service.clone();
        shell.new_command_noargs("list_rider_key", "List 1_000_000 rider key", move |_, _|{
            let mut client = client.clone();
            tokio::spawn(async move {
                let result = client.list(KeysRequest { from: Some(0), count: 1_000_000.into() }).await.expect("Server responds with error");
                let keys = result.into_inner().keys;
                println!("keys: [{}]", keys.join(", "));
                println!("Rider count is: {}", keys.len());
            });
            Ok(())
        });
    }
    {
        let client = account_service.clone();
        shell.new_command_noargs("list_random_rider", "Randomly get 1_000 Rider at the same time and repeat 100 times so all randomly listed Rider count is corresponds 100_000.", move |_, _|{
            let mut client = client.clone();
            tokio::spawn(async move {
                let (millis, _) = measure_time_mills(async {
                    let tasks = Vec::from_iter((0..100u16).map(|index|{
                        let mut client = client.clone();
                        println!("Start of task {}", index);
                        tokio::spawn(async move {
                            let keys = client.list(KeysRequest::default()).await.expect("Server responds error result").into_inner().keys;
                            if keys.len() == 0 {
                                println!("There are no Riders.");
                                return;
                            }
                            let (milllis, _) = measure_time_mills(async {
                                for i in 0..1000u16 {
                                    let random_key = {
                                        keys.get(rand::thread_rng().gen_range(0..keys.len())).expect("Cannot get random key")
                                    };
                                    let restaurant = client.get_by_id(Id{uuid: random_key.clone()}).await.expect("Cannot get user").into_inner();
                                    // if i % 100 == 0 {
                                    //     println!("Completed {} requests of task {}", i, index);
                                    // }
                                }
                            }).await;
                            println!("{}. Get random 1_000 Rider takes {} milliseconds", index + 1, milllis);
                        })
                    }));
                    for (index, task) in tasks.into_iter().enumerate() {
                        task.await.unwrap();
                    }
                }).await;
                println!("Done with {} millisecond.", millis);
            });
            Ok(())
        });
    }
    {
        let client = account_service.clone();
        shell.new_command("get_rider_by_id", "Randomly get 1_000 Rider at the same time and repeat 20 times so all randomly listed Rider count is corresponds 200_00.", 1, move |_, _, args|{
            let mut client = client.clone();
            let uuid = args.first().unwrap().to_string();
            tokio::spawn(async move {
                let (millis, _) = measure_time_mills(async {
                    if let Ok(user) = client.get_by_id(Id{uuid: uuid.clone()}).await {
                        println!("{}", serde_json::to_string_pretty(&user.into_inner()).expect("Cannot serialize to json"));
                    } else {
                        println!("There is no such Rider of uuid: {}", uuid);
                    }
                }).await;
                println!("Done with {} milliseconds.", millis);
            });
            Ok(())
        });
    }
}
